﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using SPMS.Log.Core;
using SPMS.Log.Utility;

namespace SPMS.Log.Appender
{
    /// <summary>
    /// 文件日志记录器
    /// </summary>
    /// <remarks>使用文件来保存日志信息的日志记录器</remarks>
    /// <history>
    /// [zanezeng]               2009/9/25 16:45    创建
    /// </history>
    public class FileLogAppender : LiteralLogAppender
    {
        #region ---Constructor

        /// <summary>
        /// 初始化类型<see cref="FileLogAppender"/>的新实例
        /// </summary>
        /// <param name="name">The name.</param>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/10/10 10:13    创建
        /// </history>
        public FileLogAppender(string name)
            : base(name)
        {
        }

        #endregion

        #region ---Protected Method

        /// <summary>
        /// 准备用于接收日志信息的写入器
        /// </summary>
        /// <returns>用于接收日志信息的写入器</returns>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/9/28 10:56    创建
        /// </history>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/9/28 12:19    创建
        /// </history>
        protected override SafeTextWriter PrepareTextWriter()
        {
            //判断文件写入器是否已经创建
            if (null != this.TextWriter && null != this.m_FileStream && !this.m_FileStream.SafeFileHandle.IsClosed)
            {
                //如果已经创建，则直接返回写入器
                return this.TextWriter;
            }

            //判断文件名是否指定
            if (string.IsNullOrEmpty(this.FileName))
            {
                //写入错误提示
                this.ErrorHandler.HandleError(string.Format(Log.Properties.Resources.MSG_APPENDER_FILENAME_REQUIRED, this.Name));

                //返回空的写入器
                return null;
            }

            //用于保存文件路径
            string filePath = this.FileName;

            //判断文件名称是否为绝对文件名
            if (!Path.IsPathRooted(filePath))
            {
                //以启动路径作为相对路径的基路径
                filePath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, filePath);
            }

            //获得文件夹的路径
            string directoryName = Path.GetDirectoryName(filePath);

            //判断文件夹是否存在
            if (!Directory.Exists(directoryName))
            {
                //如果文件夹不存在，则创建文件夹
                Directory.CreateDirectory(directoryName);
            }

            //设置是否新建
            this.m_IsCreateNew = !File.Exists(filePath);

            //创建文件流
            this.m_FileStream = new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);

            //准备文件访问互斥体
            this.PrepareFileAccessMutex(filePath);

            //阻止回收流
            GC.SuppressFinalize(this.m_FileStream);

            //创建文本写入器
            StreamWriter writer = new StreamWriter(this.m_FileStream, this.Encoding);

            //返回安全写入器
            return new SafeTextWriter(writer);
        }

        /// <summary>
        /// 通知输出给定的日志条目
        /// </summary>
        /// <param name="entry">要输出的日志条目</param>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/9/25 15:26    创建
        /// </history>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/9/28 10:53    创建
        /// </history>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/11/6 11:02    创建
        /// </history>
        protected override void OnAppend(LogEntry entry)
        {
            //移动到文件末尾防止重叠写入
            this.m_FileStream.Seek(0, SeekOrigin.End);

            //调用基类实现
            base.OnAppend(entry);

            //判断是否需要强制写入
            if (!this.AutoFlush)
            {
                //强制写入
                this.Flush();
            }
        }

        /// <summary>
        /// 添加日志记录的页头
        /// </summary>
        /// <param name="writer">用于写入页头的文字写入器</param>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/9/28 11:39    创建
        /// </history>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/9/28 13:47    创建
        /// </history>
        protected override void AppendHeader(TextWriter writer)
        {
            //判断是否新建
            if (this.m_IsCreateNew)
            {
                //移动到文件末尾防止重叠写入
                this.m_FileStream.Seek(0, SeekOrigin.End);

                //调用基类实现，写入日志页头
                base.AppendHeader(writer);

                //判断是否需要强制写入
                if (!this.AutoFlush)
                {
                    //强制写入
                    this.Flush();
                }
            }
        }

        /// <summary>
        /// 添加日志记录的页脚
        /// </summary>
        /// <param name="writer">用于写入页脚的文字写入器</param>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/9/28 11:40    创建
        /// </history>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/11/6 11:02    创建
        /// </history>
        protected override void AppendFooter(TextWriter writer)
        {
            //移动到文件末尾防止重叠写入
            this.m_FileStream.Seek(0, SeekOrigin.End);

            //调用基类实现，写入日志页脚
            base.AppendFooter(writer);

            //判断是否需要强制写入
            if (!this.AutoFlush)
            {
                //强制写入
                this.Flush();
            }
        }

        /// <summary>
        /// 通知关闭日志输出器
        /// </summary>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/9/25 15:06    创建
        /// </history>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/9/28 10:53    创建
        /// </history>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/11/19 16:42    创建
        /// </history>
        protected override void OnClose()
        {
            //调用基类实现
            base.OnClose();

            //关闭文件访问互斥体
            if (null != this.m_FileAccessMutex && !this.m_FileAccessMutex.SafeWaitHandle.IsClosed)
            {
                //设置允许回收文件互斥体
                GC.ReRegisterForFinalize(this.m_FileAccessMutex);

                //设置为NULL
                this.m_FileAccessMutex = null;
            }
        }

        /// <summary>
        /// 获得用于日志记录器创建同步区域所用的同步上下文
        /// </summary>
        /// <returns>用于日志记录器创建同步区域所用的同步上下文</returns>
        /// <remarks>默认返回空白同步上下文</remarks>
        /// <history>
        /// [zanezeng]               2010/1/6 10:21    创建
        /// </history>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2010/1/14 9:54    创建
        /// </history>
        protected override ISynchronizationContext GetSynchronizationContext()
        {
            //返回同步上下文
            return this.m_SynchronizationContext;
        }

        #endregion

        #region ---Property

        /// <summary>
        /// 获得或者设置用于记录日志的文件的文件名
        /// </summary>
        /// <value>用于记录日志的文件的文件名</value>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/9/28 12:23    创建
        /// </history>
        public string FileName
        {
            get
            {
                return this.m_FileName;
            }
            set
            {
                //记录文件名称
                this.m_FileName = value;

                //重置记录器
                this.Reset();
            }
        }

        /// <summary>
        /// 获得或者设置文件内容编码
        /// </summary>
        /// <value>文件内容编码</value>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/9/28 13:43    创建
        /// </history>
        public Encoding Encoding
        {
            get
            {
                //判断是否设置了编码
                if (null == this.m_Encoding)
                {
                    //默认为UTF8
                    return Encoding.UTF8;
                }

                //返回设置的编码
                return this.m_Encoding;
            }
            set
            {
                this.m_Encoding = value;
            }
        }

        /// <summary>
        /// 获得当前打开的文件流
        /// </summary>
        /// <value>当前打开的文件流</value>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/10/10 14:01    创建
        /// </history>
        protected FileStream FileStream
        {
            get
            {
                return this.m_FileStream;
            }
        }

        #endregion

        #region ---Private Method

        /// <summary>
        /// 为给定文件名称的文件创建互斥体
        /// </summary>
        /// <param name="fileName">要创建互斥体的文件名称</param>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/11/6 11:05    创建
        /// </history>
        private void PrepareFileAccessMutex(string fileName)
        {
            //判断文件访问互斥体是否已经创建
            if (null != this.m_FileAccessMutex && !this.m_FileAccessMutex.SafeWaitHandle.IsClosed)
            {
                //设置允许回收文件互斥体
                GC.ReRegisterForFinalize(this.m_FileAccessMutex);

                //设置为NULL
                this.m_FileAccessMutex = null;
            }

            //获得互斥体的名称
            string mutexName = @"Global\" + SPMS.Utility.CryptographyUtility.GetMD5Hash(fileName.ToUpper());

            //创建文件访问互斥体
            this.m_FileAccessMutex = new System.Threading.Mutex(false, mutexName);

            //创建同步上下文
            this.m_SynchronizationContext = new MutexSynchronizationContext(this.m_FileAccessMutex, FILEMUTEX_WAIT_TIMEOUT);

            //阻止回收文件访问互斥体
            GC.SuppressFinalize(this.m_FileAccessMutex);
        }

        #endregion

        #region ---Field

        /// <summary>
        /// 日志
        /// </summary>
        private string m_FileName;

        /// <summary>
        /// 用于记录文件是否新建
        /// </summary>
        private bool m_IsCreateNew;

        /// <summary>
        /// 文件流
        /// </summary>
        private FileStream m_FileStream;

        /// <summary>
        /// 文件编码
        /// </summary>
        private Encoding m_Encoding;

        /// <summary>
        /// 文件访问互斥体，用于保证对同一个文件的顺序访问
        /// </summary>
        private System.Threading.Mutex m_FileAccessMutex;

        /// <summary>
        /// 同步上下文
        /// </summary>
        private MutexSynchronizationContext m_SynchronizationContext;

        /// <summary>
        /// 文件访问互斥体名称哈希算法
        /// </summary>
        private readonly static System.Security.Cryptography.HashAlgorithm s_HashAlgorithm = System.Security.Cryptography.MD5.Create();

        /// <summary>
        /// 文件互斥体等待超时时间，默认为3秒
        /// </summary>
        private const int FILEMUTEX_WAIT_TIMEOUT = 3 * 1000;

        #endregion
    }
}
